package net.guerlab.sdk.dingtalk.client;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import net.guerlab.sdk.dingtalk.DingTalkException;
import net.guerlab.sdk.dingtalk.request.AbstractDingTalkRequest;
import net.guerlab.sdk.dingtalk.request.AbstractRequest;
import net.guerlab.sdk.dingtalk.request.AbstractSnsRequest;
import net.guerlab.sdk.dingtalk.request.GetTokenRequest;
import net.guerlab.sdk.dingtalk.response.AbstractResponse;
import net.guerlab.sdk.dingtalk.response.GetTokenResponse;
import net.guerlab.sdk.dingtalk.storage.DingTalkConfigStorage;
import net.guerlab.sdk.dingtalk.utils.SignUtils;

import java.util.concurrent.locks.Lock;

/**
 * 抽象钉钉请求客户端
 *
 * @author guer
 */
public abstract class AbstractDingTalkClient implements DingTalkClient {

    /**
     * objectMapper
     */
    protected final ObjectMapper objectMapper = new ObjectMapper();

    protected DingTalkConfigStorage dingTalkConfigStorage;

    public AbstractDingTalkClient() {
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
        objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
    }

    @Override
    public <RS extends AbstractResponse> RS execute(AbstractDingTalkRequest<RS> request) {
        if (request.needAccessToken()) {
            request.getRequestParams().put("access_token", getAccessToken());
        }
        if (request.needAppkey()) {
            request.getRequestParams().put("appkey", getAppKey());
        }
        if (request.needAppSecret()) {
            request.getRequestParams().put("appsecret", getAppSecret());
        }
        return executeWithHttpRequest(request, request.getRequestUri());
    }

    @Override
    public <RS extends AbstractResponse> RS execute(AbstractSnsRequest<RS> request) {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String signature = SignUtils.sign(timestamp, getAppSecret());
        request.getRequestParams().put("signature", signature);
        request.getRequestParams().put("timestamp", timestamp);
        request.getRequestParams().put("accessKey", getAppKey());
        return executeWithHttpRequest(request, request.getRequestUri());
    }

    /**
     * 执行请求
     *
     * @param request
     *         请求
     * @param uri
     *         请求地址
     * @param <RS>
     *         响应
     * @return 响应
     */
    protected abstract <RS extends AbstractResponse> RS executeWithHttpRequest(AbstractRequest<RS> request, String uri);

    @Override
    public String getAccessToken(boolean forceRefresh) throws DingTalkException {
        final DingTalkConfigStorage config = this.getDingTalkConfigStorage();
        if (config.isAccessTokenExpired() || forceRefresh) {
            Lock lock = config.getAccessTokenLock();
            lock.lock();
            try {
                if (!config.isAccessTokenExpired() && !forceRefresh) {
                    return config.getAccessToken();
                }

                GetTokenRequest request = new GetTokenRequest();

                request.setAppkey(config.getAppKey());
                request.setAppsecret(config.getAppSecret());

                GetTokenResponse response = execute(request);

                config.updateAccessToken(response.getAccessToken(), response.getExpiresIn());

                return config.getAccessToken();
            } finally {
                lock.unlock();
            }
        }
        return config.getAccessToken();
    }

    @Override
    public String getAppKey() {
        return dingTalkConfigStorage.getAppKey();
    }

    @Override
    public String getAppSecret() {
        return dingTalkConfigStorage.getAppSecret();
    }

    @Override
    public DingTalkConfigStorage getDingTalkConfigStorage() {
        return dingTalkConfigStorage;
    }

    @Override
    public void setDingTalkConfigStorage(DingTalkConfigStorage dingTalkConfigStorage) {
        if (dingTalkConfigStorage != null) {
            this.dingTalkConfigStorage = dingTalkConfigStorage;
        }
    }
}
